import json
import os
import re # 导入正则表达式，用于解析文件名
from agent import (
    agent_planer,
    agent_testcase_generator, # 导入 agent_testcase_generator
    agent_evaluator          # 导入 agent_evaluator
)
import user
from structs import UserStatus, Plan, Project # 导入 UserStatus, Plan, Project
from project import (
    get_project_list as _get_project_list,
    new_project as _new_project,
    get_project_content as _get_project_content,
    save_new_project as _save_new_project,
    save_project as _save_project,
    PROJECT_BASE_DIR
)
from codetest import execute_and_test_python_code # 导入 execute_and_test_python_code
USER_DATA_DIR = "user_data"
PLANS_DIR = "plans"

# 确保目录存在
os.makedirs(USER_DATA_DIR, exist_ok=True)
os.makedirs(PLANS_DIR, exist_ok=True)
## 逻辑：
## user下id ,passwd是用户基本数据
## lang direction是“当前的数据”
def get_user_data(current_user_id: str) -> dict:
    """读取用户数据文件，如果不存在则返回默认值。"""
    file_path = os.path.join(USER_DATA_DIR, f"{current_user_id}.json")
    default_data = {
        "is_new_user": True,
        "lang": "", # 开始时，lang/direction 为空，直到被选择/生成
        "direction": ""
    }
    if os.path.exists(file_path):
        try:
            with open(file_path, "r", encoding="utf-8") as f:
                data = json.load(f)
                # 确保必要字段存在，提供默认值
                # data.setdefault("user_level", default_data["user_level"]) # 不再需要
                data.setdefault("is_new_user", default_data["is_new_user"])
                data.setdefault("lang", default_data["lang"])
                data.setdefault("direction", default_data["direction"])
                return data
        except (json.JSONDecodeError, IOError) as e:
            print(f"读取用户数据时出错 {current_user_id}: {e}")
            # 如果读取失败，返回默认值
            return default_data
    else:
        # 文件不存在，视为新用户并使用默认值
        return default_data

def save_user_data(current_user_id: str, data: dict):
    """将用户数据保存到文件。"""
    file_path = os.path.join(USER_DATA_DIR, f"{current_user_id}.json")
    try:
        with open(file_path, "w", encoding="utf-8") as f:
            json.dump(data, f, ensure_ascii=False, indent=4)
    except IOError as e:
        print(f"写入用户数据时出错 {current_user_id}: {e}")

def process_lang_direction(current_user_id: str, lang: str, direction: str): # 添加 current_user_id 参数
    """处理语言和方向，生成并保存计划。"""
    plan = agent_planer(lang, direction)

    if plan is None:
        raise ValueError(f"无法生成计划：lang={lang}, direction={direction}")

    # 将plan保存到文件
    # 在文件名中使用direction，必要时替换可能有问题的字符
    safe_direction = direction.replace('/', '_').replace('\\', '_') # 基本清理
    file_path = os.path.join(PLANS_DIR, f"{current_user_id}_{lang}_{safe_direction}.json") # 使用传入的 current_user_id
    try:
        # 确保计划对象可以被序列化 (例如，具有 __dict__ 或是一个 dict)
        plan_data = plan.__dict__ if hasattr(plan, '__dict__') else plan
        # 初始化 user_level
        plan_data["user_level"] = 0
        with open(file_path, "w", encoding="utf-8") as f:
            json.dump(plan_data, f, ensure_ascii=False, indent=4)


    except (IOError, TypeError) as e: # 添加 TypeError 以处理序列化问题
        print(f"写入计划文件时出错: {e}")
        raise # 重新抛出异常以便调用者处理

    # 同时更新用户数据中的新生成/选择的计划的 lang/direction
    user_data = get_user_data(current_user_id) # 使用传入的 current_user_id
    user_data["lang"] = lang
    user_data["direction"] = direction
    save_user_data(current_user_id, user_data) # 使用传入的 current_user_id

    return plan

# 修改为接受 current_user_id, lang 和 direction
def save_user_settings(current_user_id: str, lang: str, direction: str): # 添加 current_user_id 参数
    """保存用户的语言和方向设置。"""
    user_data = get_user_data(current_user_id) # 使用传入的 current_user_id
    # user_data["user_level"] = user_level # 不再保存 level
    user_data["lang"] = lang
    user_data["direction"] = direction
    # is_new_user 由 set_user_not_new 单独处理
    save_user_data(current_user_id, user_data) # 使用传入的 current_user_id

def set_user_not_new(current_user_id: str): # 添加 current_user_id 参数
    """将用户标记为非新用户。"""
    user_data = get_user_data(current_user_id) # 使用传入的 current_user_id
    user_data["is_new_user"] = False
    save_user_data(current_user_id, user_data) # 使用传入的 current_user_id

def get_user_status(current_user_id: str) -> dict: # 添加 current_user_id 参数，返回字典
    """获取用户的状态信息 (is_new, lang, direction, user_level)。"""
    user_data = get_user_data(current_user_id) # 使用传入的 current_user_id
    is_new_user = user_data.get("is_new_user", True)
    lang = user_data.get("lang")
    direction = user_data.get("direction")
    user_level = None # 默认为 None

    # 如果 lang 存在，尝试获取当前计划的 user_level
    if lang:
        # 使用 get_plan_data 获取计划信息，它已经包含了读取和处理 user_level 的逻辑
        plan_data = get_plan_data(current_user_id) # 使用传入的 current_user_id
        if plan_data:
            user_level = plan_data.get("user_level", 0) # 从计划数据获取，默认为 0

    # 返回符合 UserStatus 结构的字典
    status = UserStatus(
        is_new_user=is_new_user,
        lang=lang,
        direction=direction,
        user_level=user_level
    )
    return status.__dict__ # 返回字典

# 新函数，列出用户的可用计划文件
def list_user_plans(current_user_id: str) -> list[dict]:
    """扫描计划目录，返回用户的所有计划（语言和方向）。"""
    available_plans = []
    try:
        # 正则表达式捕获文件名中的 lang 和 direction，例如 "123_python_data structures.json"
        # 假设 lang 不包含下划线，direction 可能包含。
        # 它捕获第二个下划线之后的所有内容，直到 ".json" 作为 direction。
        pattern = re.compile(rf"^{re.escape(current_user_id)}_([^_]+)_(.*)\.json$")
        for filename in os.listdir(PLANS_DIR):
            match = pattern.match(filename)
            if match:
                lang = match.group(1)
                # 如果方向被清理过，则恢复原始方向（简单情况）
                direction = match.group(2).replace('_', '/')
                available_plans.append({"lang": lang, "direction": direction})
    except OSError as e:
        print(f"扫描计划目录时出错 {PLANS_DIR}: {e}")
    return available_plans

# 修改后的函数，根据当前用户设置获取计划数据
def get_plan_data(current_user_id: str) -> dict | None:
    """获取用户当前设置对应的计划数据 (plans, intro, user_level)。""" # 添加 user_level 到 docstring
    user_data = get_user_data(current_user_id)
    lang = user_data.get("lang")
    direction = user_data.get("direction", "") # 如果缺失，默认为空字符串

    if not lang: # 需要 lang 来标识计划
        print(f"用户 {current_user_id} 的当前语言未设置")
        return None

    # 使用清理过的 direction 进行文件名匹配
    safe_direction = direction.replace('/', '_').replace('\\', '_')
    file_path = os.path.join(PLANS_DIR, f"{current_user_id}_{lang}_{safe_direction}.json")

    if os.path.exists(file_path):
        try:
            with open(file_path, "r", encoding="utf-8") as f:
                plan_data = json.load(f)
                # 基本验证：检查是否包含 'plans' 和 'intro' 键
                if "plans" in plan_data and "intro" in plan_data:
                    # *** 添加了更严格的验证 ***
                    # 检查 'plans' 和 'intro' 是否为列表且长度是否为 10
                    if (isinstance(plan_data.get("plans"), list) and
                        isinstance(plan_data.get("intro"), list) and
                        len(plan_data["plans"]) == 10 and
                        len(plan_data["intro"]) == 10):
                        # 检查并添加 user_level，默认为 0
                        plan_data.setdefault("user_level", 0)
                        return plan_data
                    else:
                        # 如果列表类型或长度不正确，则认为计划文件已损坏
                        print(f"错误：用户 {current_user_id} 的计划文件 ('{file_path}') 的 'plans' 或 'intro' 列表已损坏或不完整。")
                        # 可以选择性地记录列表的实际类型和长度以进行调试
                        # print(f"详细信息: plans type={type(plan_data.get('plans'))}, len={len(plan_data.get('plans', []))}; "
                        #       f"intro type={type(plan_data.get('intro'))}, len={len(plan_data.get('intro', []))}")
                        return None # 返回 None 表示数据无效
                else:
                    # 如果缺少 'plans' 或 'intro' 键
                    print(f"用户 {current_user_id} 的计划文件 ('{file_path}') 缺少 'plans' 或 'intro' 键。")
                    return None
        except (json.JSONDecodeError, IOError) as e:
            print(f"读取计划文件 '{file_path}' 时出错: {e}")

def save_plan_level(current_user_id: str, lang: str, direction: str, new_level: int):
    """更新指定计划文件中的 user_level。"""
    if not lang or not direction: # 在尝试使用它们之前检查 lang 和 direction
        print(f"错误：保存计划等级时 lang 或 direction 为空。 User: {current_user_id}")
        return False # 表示失败

    safe_direction = direction.replace('/', '_').replace('\\', '_')
    file_path = os.path.join(PLANS_DIR, f"{current_user_id}_{lang}_{safe_direction}.json")

    if os.path.exists(file_path):
        try:
            # 先读取现有数据
            with open(file_path, "r", encoding="utf-8") as f:
                plan_data = json.load(f)

            # 更新 level
            plan_data["user_level"] = new_level

            # 写回文件
            with open(file_path, "w", encoding="utf-8") as f:
                json.dump(plan_data, f, ensure_ascii=False, indent=4)
            return True # 表示成功
        except (json.JSONDecodeError, IOError, KeyError) as e: # 添加 KeyError 以防 plan_data 结构意外
            print(f"更新计划文件 '{file_path}' 的 level 时出错: {e}")
            return False # 表示失败
    else:
        print(f"尝试更新 level 时计划文件未找到: {file_path}")
        return False # 表示失败

def login(id: str, passwd: str):
    
    # global user_id # 不再需要设置全局变量
    success,errormsg = user.login(id, passwd)
    # 在 main.py 中处理 user_id 的存储（例如，在 session 中）
    return success,errormsg
def register(id: str, passwd: str):
    return user.register(id, passwd)
def get_project_list_wrapper(user_id: str, lang: str, direction: str, step: int) -> list[dict]:
    """包装project.py的get_project_list函数"""
    return _get_project_list(user_id, lang, direction, step)

def new_project_wrapper(plan_data: dict, user_level: int,proj_name :str) -> dict | None:
    """包装project.py的new_project函数"""
    try:
        plan = Plan(
            lang=plan_data['lang'],
            方向=plan_data['方向'],
            plans=plan_data['plans'],
            intro=plan_data['intro'],
            user_level=user_level
        )
        project = _new_project(plan, user_level)
        if project:
            return {
                "name": proj_name,
                "code": project.code,
                "md": project.md,
                "answer": project.answer
            }
        return None
    except Exception as e:
        print(f"Error in new_project_wrapper: {e}")
        return None

def get_project_content_wrapper(user_id: str, lang: str, direction: str, step: int, project_index: int) -> dict | None:
    """包装project.py的get_project_content函数"""
    project = _get_project_content(user_id, lang, direction, step, project_index)
    if project:
        return {
            "name": project.name,
            "code": project.code,
            "md": project.md,
            "answer": project.answer
        }
    return None

# 这是新建project后调用的！！
def save_new_project_wrapper(user_id: str, lang: str, direction: str, step: int, project_data: dict) -> bool:
    """包装project.py的save_new_project函数，由底层确定project_index"""
    try:
        project = Project(
            code=project_data['code'],
            md=project_data['md'],
            name=project_data['name'],
            answer=project_data['answer']
        )
        # 调用 project.py 中的 save_project 函数，不再传递 project_index
        _save_new_project(user_id, lang, direction, step, project)
        return True
    except Exception as e:
        print(f"Error in save_project_wrapper: {e}")
        return False

def save_project_wrapper(user_id: str, lang: str, direction: str, step: int, project_data: dict,project_index: int) -> bool:
    """包装project.py的save_project函数"""
    try:
        project = Project(
            code=project_data['code'],
            md=project_data['md'],
            name=project_data['name'],
            answer=project_data['answer']
        )
        _save_project(user_id, lang, direction, step, project,project_index)
        return True
    except Exception as e:
        print(f"Error in save_project_wrapper: {e}")
        return False


# --- 新增 API 包装函数 ---

def process_exec_api(user_id: str, lang: str, direction: str, step: int, project_index: int, user_code: str) -> dict:
    """
    处理 /api/exec 请求的逻辑。
    生成测试用例，直到找到能通过答案代码的用例，然后用这些用例测试用户代码。
    """
    project_data = get_project_content_wrapper(user_id, lang, direction, step, project_index)
    if not project_data:
        raise ValueError("无法获取项目内容")

    # 确保 project_data 包含 answer
    if 'answer' not in project_data or not project_data['answer']:
         raise ValueError("项目缺少答案代码，无法生成和验证测试用例")

    # 创建一个临时的 Project 对象用于生成测试用例，因为 agent_testcase_generator 需要 Project 对象
    # 注意：这里的 name 和 md 可能与实际项目不同，但对于生成测试用例通常不重要
    # 如果 agent_testcase_generator 确实需要准确的 name 和 md，需要调整
    temp_project_for_testgen = Project(
        code=project_data.get('code', ''), # 初始代码可能需要
        md=project_data.get('md', ''),     # MD描述可能需要
        name=project_data.get('name', ''),
        answer=project_data['answer']      # 必须有答案代码
    )


    max_attempts = 5 # 防止无限循环
    valid_test_cases = None

    for attempt in range(max_attempts):
        print(f"尝试生成测试用例第 {attempt + 1} 次...")
        test_cases = agent_testcase_generator(temp_project_for_testgen)
        if not test_cases:
            print("未能生成测试用例，重试...")
            continue

        print("使用答案代码验证测试用例...")
        answer_result = execute_and_test_python_code(project_data['answer'], test_cases)

        if answer_result['all_passed']:
            print("找到有效的测试用例！")
            valid_test_cases = test_cases
            break
        else:
            print("测试用例未能通过答案代码，重新生成...")
            # 可选：打印失败的测试用例详情
            # from codetest import print_test_result
            # print_test_result(answer_result)


    if valid_test_cases is None:
        raise ValueError(f"在 {max_attempts} 次尝试后未能生成有效的测试用例")

    print("使用有效测试用例执行用户代码...")
    # 使用获取到的有效 test_cases 来运行用户的代码
    user_result = execute_and_test_python_code(user_code, valid_test_cases)
    return user_result

def process_evaluate_api(user_id: str, lang: str, direction: str, step: int, project_index: int, result_dict: dict, user_code: str) -> str:
    """
    处理 /api/evaluate 请求的逻辑。
    调用 agent_evaluator 进行代码评估。
    """
    project_data = get_project_content_wrapper(user_id, lang, direction, step, project_index)
    if not project_data:
        raise ValueError("无法获取项目内容")

    # 创建 Project 对象传递给 agent_evaluator
    project_for_eval = Project(
        code=project_data.get('code', ''),
        md=project_data.get('md', ''),
        name=project_data.get('name', ''),
        answer=project_data.get('answer', '')
    )

    # 调用 agent_evaluator
    # user_code 是从 main.py 传递过来的用户编辑器中的最新代码
    evaluation = agent_evaluator(project_for_eval, user_code, result_dict)

    return evaluation
